home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Diamond Collection / The Diamond Collection (Software Vault)(Digital Impact).ISO / cdr27 / qtxt100.zip / QTXT.PAS < prev    next >
Pascal/Delphi Source File  |  1995-02-24  |  15KB  |  467 lines

  1. PROGRAM QTXT; {v1.00 - Free DOS utility: Converts .QWK packets to text files.}
  2. {$M 5120,0,102400}  { 100k reserved for data }
  3. {$N-,E- no math support needed}
  4. {$X- function calls may not be discarded}
  5. {$I-} {disable I/O checking - trap errors by checking IOResult}
  6.  
  7. {===========================================================================}
  8.                        (** Global declarations ... **)
  9. {===========================================================================}
  10.  
  11. USES    DOS, CRT;
  12.  
  13. CONST   cursorState : byte = 1;  {0..3}
  14.         cursorData : array [0..3] of char = (#179, #47, #196, #92);
  15.         MaxConfs = 5337;
  16.         ConfNameLength = 12;
  17.  
  18. TYPE    ConfNameArray=Array[0..MaxConfs] of Array[1..ConfNameLength] of char;
  19.  
  20. VAR     UnArcQWK : pathstr;
  21.         BBSid    : string[12];
  22.         CNames   : ConfNameArray;
  23.  
  24. {===========================================================================}
  25.                    (** Custom help & exit procedure ... **)
  26. {===========================================================================}
  27.  
  28. var SavedExitProc: Pointer;
  29. procedure cursorOn; forward;
  30.  
  31. procedure CustomExit; far;
  32. {---- Always exit through here ----}
  33. const
  34.   progdesc = 'QTXT v1.00 - Free DOS utility: Converts .QWK packets to text files.';
  35.   author   = 'February 24, 1995. Copyright (c) 1995 by David Daniel Anderson - Reign Ware.';
  36.   usage    = 'Usage:    QTXT <QWKpacket(s)>';
  37.   example  = 'Example:  QTXT c:\qwks\*.qwk';
  38.   note     = 'Note: DOS wildcards may be used when specifying the QWKpackets.';
  39.  
  40. var
  41.   message: string[79];
  42. begin
  43.   ExitProc := SavedExitProc;
  44.   cursorOn;
  45.   if (ExitCode > 0) then begin
  46.     writeln(progdesc);
  47.     writeln(author);    writeln;
  48.     writeln(usage);
  49.     writeln(example);   writeln;
  50.     writeln(note);      writeln;
  51.   end;
  52.   if ErrorAddr <> nil then
  53.   begin
  54.     writeln('An unanticipated error occurred, please contact DDA with the following data:');
  55.     writeln('Address = ', Seg(ErrorAddr^), ':', Ofs(ErrorAddr^));
  56.     writeln('Code    = ', Exitcode);
  57.     ErrorAddr := nil;
  58.   end
  59.   else
  60.     if (ExitCode > 0) and (ExitCode < 255) then begin
  61.        case ExitCode of
  62.          2 : message := 'No files found.  First parameter must be a valid file specification.';
  63.          5 : message := 'Not enough memory to extract MESSAGES.DAT - aborting!';
  64.          6 : message := 'You cannot just specify a path, add "*.*" or "\*.*" for all files.';
  65.          7 : message := 'File handling error.  Text file is most likely incomplete - or nonexistent.';
  66.        else  message := 'Unknown error.';
  67.        end;
  68.        writeln (#7, 'Error encountered, number ',ExitCode,':'); writeln (message);
  69.     end;
  70. end;
  71.  
  72. {===========================================================================}
  73.                       (** Supporting subroutines ... **)
  74. {===========================================================================}
  75.  
  76. procedure iocheck(const iores :byte);
  77. begin
  78.   if iores <> 0 then halt(7);
  79. end;
  80.  
  81. procedure cursorOn;assembler;asm
  82.   mov ah,3; mov bh,0; int $10; and ch,not $20; mov ah,1; int $10;
  83. end;
  84.  
  85. procedure cursorOff;assembler;asm
  86.   mov ah,3; mov bh,0; int $10; or ch,$20; mov ah,1; int $10;
  87. end;
  88.  
  89. procedure updateCursor;
  90. begin
  91.   cursorState := succ(cursorState) and 3;
  92.   write(cursorData[cursorState], ^H);
  93. end;
  94.  
  95. FUNCTION leadingzero (CONST w: word): STRING;
  96. VAR
  97.   s : STRING;
  98. BEGIN
  99.   str (w :0, s);
  100.   IF (length (s) = 1) THEN
  101.     s:='0'+s;
  102.   leadingzero:=s;
  103. END;
  104.  
  105. Function RPad(bstr: string; Const len: byte): string;
  106. Begin
  107.   while (length(bstr) < len) do
  108.     bstr := bstr + #32;
  109.   RPad := bstr;
  110. End;
  111.  
  112. FUNCTION RTrim(InStr: STRING): STRING;
  113. BEGIN
  114.   WHILE (LENGTH(InStr) > 0) AND (InStr[LENGTH(InStr)] in [#0,#9,#32]) DO
  115.     DEC(InStr[0]);
  116.   RTrim := InStr;
  117. END;
  118.  
  119. function Squeeze(ss:string): string;
  120. var
  121.   controlCHAR: char;
  122. begin
  123.   for controlCHAR:=#0 to #31 do
  124.   while (ord(ss[0]) > 0) and (Pos(controlCHAR,ss) > 0) do
  125.     ss[Pos(controlCHAR,ss)]:=#32;
  126.   while (ord(ss[0]) > 0) and (ss[1]=#32) do
  127.     delete(ss,1,1);
  128.   ss := RTrim(ss);
  129.   Squeeze:=ss
  130. end;
  131.  
  132. function fileexists(const filename: pathstr): boolean;
  133. var
  134.   attr : word;
  135.   f    : file;
  136. begin
  137.   assign (f, filename);
  138.   getfattr (f, attr);
  139.   if (DOSerror <> 0) OR ((attr and directory) = directory) then
  140.     fileexists := FALSE
  141.   else
  142.     fileexists := TRUE;
  143. end;
  144.  
  145. procedure EraseFile(const MSGFile : string);
  146. var
  147.   df : file;
  148. begin
  149.   if fileexists(MSGFile) then begin
  150.     assign(df, MSGFile);
  151.     erase(df); iocheck(ioresult);
  152.   end;
  153. end;
  154.  
  155. {===========================================================================}
  156.                        (** Primary subroutines ... **)
  157. {===========================================================================}
  158.  
  159. procedure InitArcQWK;
  160. var
  161.   epath, cpath  : pathstr;
  162.     {epath & cpath are fully qualified pathnames of .exe & .cfg files}
  163.   edir: dirstr; ename: namestr; eext: extstr;
  164.   config        : text;
  165.   configline    : string[80];
  166. begin
  167.   epath := (paramstr (0));
  168.   fsplit(fexpand(epath),edir,ename,eext); { break up path into components }
  169.   cpath := edir+ename+'.cfg';
  170.  
  171.   UnArcQWK:='pkunzip -# -o';
  172.   if fileexists(cpath) then
  173.   begin
  174.     assign (config, cpath);
  175.     reset (config); iocheck(ioresult);
  176.     repeat  { find vars }
  177.       readln(config,configline);
  178.       if (length(configline) > 10) and
  179.         (copy(configline,1,9) = 'UNARCQWK=') then
  180.         UnArcQWK := Copy(configline,10,length(configline)-9);
  181.     until eof(config); { loop back to read another line }
  182.     close (config);
  183.   end;
  184. end;
  185. {===========================================================================}
  186.  
  187. function GetQWKdir(const pstr: string; var QP: pathstr): dirstr;
  188. var
  189.   QWKpath   : pathstr;    { QWK file path,          }
  190.   QWKdir    : dirstr;     {             directory,  }
  191.   QWKname   : namestr;    {             name,       }
  192.   QWKext    : extstr;     {             extension.  }
  193. BEGIN
  194.   QWKpath:=pstr;
  195.   if QWKpath[1] in ['/','-'] then halt(255);
  196.   fsplit(fexpand(QWKpath),QWKdir,QWKname,QWKext);
  197.     if (QWKname = '')  then halt(6);
  198.   QP:=QWKpath;
  199.   GetQWKdir:=QWKdir;
  200. END;
  201.  
  202. function ExtractDAT(const QWKfile, DATfileName : string): boolean;
  203. var
  204.   x,y : byte;
  205. begin
  206.   x:=WhereX;
  207.   y:=WhereY;
  208.   swapvectors;
  209.      exec (getenv ('COMSPEC'),' /c '+UnArcQWK+' '+QWKfile+' '+DATfileName);
  210.      if doserror <> 0 then halt(5);
  211.   swapvectors;
  212.   GotoXY(x,y);
  213.   ClrEOL;
  214.   cursorOff;
  215.   ExtractDAT:=fileexists(DATfileName)
  216. end;
  217. {===========================================================================}
  218.  
  219. Function InitConfNamesArray(Const QWKpath, CNFFileName: string): string;
  220. var x,y: word;
  221.   CNFFile  : text;
  222.   CNameStr : string;
  223.   CNumb,
  224.   CNameInt : word;
  225.   BBSname  : string[12];
  226.   VErr     : integer;
  227. BEGIN
  228.   BBSname := 'unknown'+#32#32#32#32#32#32#32;
  229.   for x := 0 to (MaxConfs - 1) do
  230.     FillChar(CNames[x],12,#32);
  231.  
  232.   if ExtractDAT(QWKpath, CNFFileName) then begin
  233.     Assign (CNFFile, CNFFileName);
  234.     Reset (CNFFile); iocheck(ioresult);
  235.  
  236.     for x := 1 to 5 do                    { advance to BBSid }
  237.       if not EOF(CNFFile) then
  238.         Readln(CNFFile,CNameStr);
  239.  
  240.     if not EOF(CNFFile) and (Pos(',',CNameStr) > 0) then begin
  241.       Delete(CNameStr,1,Pos(',',CNameStr));
  242.       BBSname:=RPad(Squeeze(CNameStr),12);         { extract BBSname }
  243.     end;
  244.  
  245.     for x := 1 to 5 do      { advance to just before number of conferences }
  246.       if not EOF(CNFFile) then
  247.         Readln(CNFFile,CNameStr);
  248.  
  249.     if not EOF(CNFFile) then begin
  250.       Readln(CNFFile,CNameStr);           { get number of conferences }
  251.       Val(Squeeze(CNameStr),CNameInt,VErr);
  252.       if (VErr=0) then
  253.       for x := 0 to CNameInt do           { walk through conf names }
  254.         if not EOF(CNFFile) then begin
  255.           Readln(CNFFile,CNameStr);       { read conference number }
  256.           Val(Squeeze(CNameStr),CNumb,VErr);
  257.           if (VErr=0) and (not EOF(CNFFile)) then begin
  258.             Readln(CNFFile,CNameStr);     { read conference name }
  259.             for y := 1 to length(CNameStr) do
  260.               if (y <= ConfNameLength) then
  261.                 CNames[CNumb][y] := CNameStr[y];
  262.           end;
  263.         end;
  264.     end;
  265.     Close(CNFFile);
  266.     EraseFile(CNFFileName);
  267.   end;
  268.   InitConfNamesArray:=BBSname;
  269. END;
  270. {===========================================================================}
  271.  
  272. function AdjustTime(time: string): string;
  273. var ampm : char;
  274.     hour : byte;
  275.     VErr : integer;
  276. begin
  277.  ampm := 'a';
  278.  Val(Copy(time,1,2), hour, VErr);
  279.  
  280.  if (hour >= 12) then begin
  281.    ampm := 'p';
  282.    if (hour >= 13) then
  283.      hour := hour - 12;
  284.  end;
  285.  AdjustTime := LeadingZero(hour)+Copy(time,3,3)+ampm;
  286. end;
  287.  
  288. PROCEDURE ProcessHeader (var MSGFile: file; var TXTfile: text; var NumChunks:integer);
  289. CONST
  290.   herald    = '===============================================================================';
  291.   Separator = '-------------------------------------------------------------------------------';
  292.   space=#32;
  293.  
  294. (* Note: the meaning of the status flag in the header of the QWK format
  295.          specification is interpreted differently by different products.
  296.  
  297.    According to Patrick Y. Lee's "QWK Mail Packet File Layout" v1.0
  298.    and Robomail v1.30, an asterisk ('*') means private and received,
  299.                  and the plus sign ('+') means private and NOT received.
  300.  
  301.    SLMR 2.1a, SPEED and OLX v1.53 seem to agree that the meaning of the
  302.    two symbols is reversed.
  303.  
  304.    Since this is a SPEED utility, I've used the latter.  Thus, the private
  305.    and received flags will be translated into the following symbols:
  306.  
  307.               public, unread   =  ' '  (#32)
  308.               public, read     =  '-'  (#45)
  309.               private, unread  =  '*'  (#42)
  310.               private, read    =  '+'  (#43)
  311. *)
  312. TYPE
  313.   MSGDATHdr=RECORD
  314.     Status   :Char;
  315.     MSGNum   :ARRAY [1..7] OF Char;
  316.     Date     :ARRAY [1..8] OF Char;
  317.     Time     :ARRAY [1..5] OF Char;
  318.     WhoTo    :ARRAY [1..25] OF Char;
  319.     WhoFrom  :ARRAY [1..25] OF Char;
  320.     Subject  :ARRAY [1..25] OF Char;
  321.     PassWord :ARRAY [1..12] OF Char;
  322.     ReferNum :ARRAY [1..8] OF Char;
  323.     NumChunk :ARRAY [1..6] OF Char;
  324.     Alive    :Byte;
  325.     ConfNumb :Word;
  326.     Reserved :ARRAY [1..3] OF Char;
  327.   END;
  328. VAR
  329.   VErr : integer;
  330.   MessageHeader : MSGDATHdr;
  331. BEGIN
  332.   updateCursor;
  333.   BlockRead (MSGFile, MessageHeader, 1);
  334.   Val(Squeeze(MessageHeader.NumChunk), NumChunks, VErr);
  335.   if (VErr<>0) then NumChunks:=0;
  336.   IF NumChunks <> 0 THEN
  337.     WITH MessageHeader DO BEGIN
  338.       Writeln (TXTfile, herald);
  339.       Writeln (TXTfile, space:5,'Date: ', Date,
  340.                         space:4,'Time: ',AdjustTime(Time),
  341.                         space:5,'Number: ', MSGNum);
  342.       Writeln (TXTfile, space:5,'From: ', WhoFrom,
  343.                         space:5,'Refer: ', ReferNum);
  344.       Write   (TXTfile, space:7,'To: ', WhoTo,
  345.                         space:2,'Board ID: ',BBSid,
  346.                         space:4,'Recvd: ');
  347.                      IF Status IN [#32,#42,#126,#37,#33,#36] {unread symbols}
  348.                         THEN Writeln (TXTfile, 'No')
  349.                         ELSE Writeln (TXTfile, 'Yes');
  350.       Write   (TXTfile, space:2,'Subject: ', Subject,
  351.                         space:4, ConfNumb:6, ': ',CNames[ConfNumb],
  352.                         space:3,'Status: ');
  353.                      IF Status IN [#43,#42,#126,#96,#33,#35] {private symbols}
  354.                         THEN Writeln (TXTfile, 'Private')
  355.                         ELSE Writeln (TXTfile, 'Public');
  356.       Writeln (TXTfile, Separator);
  357.     END;
  358. END;
  359. {===========================================================================}
  360.  
  361. PROCEDURE ProcessMessage (var MSGFile: file; var TXTfile: text; NumChunks:Integer);
  362. var
  363.   Buff     : ARRAY [1..128] OF Char;
  364.   BuffStr  : string;
  365.   QRecs    : Integer;
  366.   BuffByte : Byte;
  367. BEGIN
  368.   BuffStr := '';
  369.   FOR QRecs := 1 TO Pred (NumChunks) DO BEGIN
  370.     BlockRead (MSGFile, Buff, 1);
  371.     FOR BuffByte := 1 TO 128 DO
  372.       IF Buff [BuffByte] = #227
  373.         THEN BEGIN
  374.           Writeln (TXTfile,RTrim(BuffStr));
  375.           BuffStr := '';
  376.         END
  377.         ELSE BuffStr := BuffStr + Buff[BuffByte];
  378.   END;
  379.   Writeln (TXTfile,RTrim(BuffStr))
  380. END;
  381. {===========================================================================}
  382.  
  383. PROCEDURE ProcessFiles (var MSGFile: file; var TXTfile: text);
  384. var
  385.   QWKrecs,
  386.   Chunks    :Integer;
  387. BEGIN
  388.     QWKrecs := 2;                         { start at RECORD #2 }
  389.     WHILE QWKrecs < FileSize (MSGFile) DO BEGIN
  390.       Seek (MSGFile, QWKrecs - 1);
  391.       ProcessHeader (MSGFile,TXTfile,Chunks);
  392.       IF Chunks <> 0
  393.         THEN ProcessMessage (MSGFile,TXTfile,Chunks)
  394.         ELSE Chunks := 1;
  395.       Inc (QWKrecs, Chunks);
  396.     END;
  397. END;
  398.  
  399. {===========================================================================}
  400.                            (** Main program ... **)
  401. {===========================================================================}
  402.  
  403. CONST
  404.   MSGFileName = 'MESSAGES.DAT';
  405.   CNFFileName = 'CONTROL.DAT';
  406.  
  407. var
  408.   MSGFile : File;
  409.   TXTfile : Text;
  410.  
  411.   QWKpath    : pathstr;    { QWK file path. }
  412.   QWKdir     : dirstr;     { QWK file dir.  }
  413.   TXTpath    : pathstr;    { TXT file path. }
  414.   fileinfo   : SearchRec;
  415.  
  416. BEGIN
  417.   SavedExitProc := ExitProc;
  418.   ExitProc := @CustomExit;
  419.   CheckBreak:=true;
  420.   cursorOff;
  421.  
  422.   if ParamCount <> 1 then halt(255);
  423.   InitArcQWK;
  424.   QWKdir:=GetQWKdir(ParamStr(1), QWKpath);
  425.  
  426.   findfirst(QWKpath, archive, fileinfo); if doserror <> 0 then halt(2);
  427.   Writeln ('QTXT v1.00 - Free QWK to TXT convertor is now working.');
  428.   while doserror = 0 do
  429.   begin
  430.     QWKpath := QWKdir + fileinfo.name;
  431.     TXTpath := fileinfo.name;
  432.       if (Pos('.',TXTpath) > 0) and (Pos('.',TXTpath) < length(TXTpath)) then
  433.         TXTpath[1+Pos('.',TXTpath)] := 'T'
  434.       else
  435.         TXTpath := TXTpath+'.T';
  436.  
  437.     Write ('Checking ', QWKpath, ' and ', TXTpath);
  438.     IF fileexists (TXTpath)
  439.       THEN Writeln (' ... text file exists - skipping.')
  440.       ELSE begin
  441.         Writeln(', done!');
  442.         EraseFile(MSGFileName);
  443.         Write('Extracting MESSAGES.DAT from ',QWKpath,' ...');
  444.         if ExtractDAT(QWKpath, MSGFileName) then begin
  445.  
  446.           Writeln(' done!');
  447.           BBSid:=InitConfNamesArray(QWKpath, CNFFileName);
  448.           Assign (MSGFile, MSGFileName);
  449.           Reset (MSGFile, 128); iocheck(ioresult);
  450.           Assign (TXTfile, TXTpath);
  451.           Rewrite (TXTfile); iocheck(ioresult);
  452.           Write('Translating messages to ',TXTpath,#32);
  453.           ProcessFiles (MSGFile, TXTfile);
  454.           Writeln(#8,', done!');
  455.           Close (MSGFile); iocheck(ioresult);
  456.           Close (TXTfile); iocheck(ioresult);
  457.  
  458.           EraseFile(MSGFileName);
  459.         end
  460.         else
  461.           writeln('- bad QWK - skipping.');
  462.       END;
  463.     findnext(fileinfo);
  464.   end;
  465.   writeln('Mission accomplished!');
  466. END.
  467.